#include "padstackdefviewer.h"

#include <QDebug>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsItemGroup>
#include <QGraphicsScene>
#include <QHeaderView>
#include <QListView>
#include <QStackedWidget>
#include <QTableView>
#include <QVBoxLayout>

#include "Ipc2581.h"
#include "Layer.h"
#include "PadstackDef.h"
#include "PadstackHoleDef.h"
#include "PadstackPadDef.h"
#include "padstackdeflistmodel.h"
#include "enumtranslator.h"
#include "Solarized.h"
#include "layerlistmodel.h"
#include "LayerListWidget.h"
#include "PadUseListModel.h"
#include "PadstackPadDetailModel.h"
#include "PadstackHoleDetailModel.h"
#include "graphicsscene.h"
#include "graphicsview.h"
#include "graphicsitem.h"
#include "GraphicsItemFactory.h"

/*
 * TODO: Add a drill layer to the layerlist
 */
PadstackDefViewer::PadstackDefViewer(QObject *parent):
    IViewer(parent),
    m_ipc(nullptr),
    m_padstackDefListModel(new PadstackDefListModel(this)),
    m_padstackDefListView(new QListView),
    m_graphicsScene(new GraphicsScene(this)),
    m_graphicsView(new GraphicsView),
    m_graphicsItemFactory(new GraphicsItemFactory(this)),
    //m_graphicsHoleLayer(nullptr),
    //m_graphicsHighlightLayer(nullptr),
    //m_graphicsShapeLayer(new QGraphicsPathItem),
    m_layerListWidget(new LayerListWidget()),
    m_layerListModel(new LayerListModel(this)),
    m_currentLayer(nullptr),
    m_padUseListModel(new PadUseListModel(this)),
    m_padUseListView(new QListView),
    m_padstackPadDefDetailModel(new PadstackPadDetailModel(this)),
    m_padstackPadDefDetailView(new QTableView),
    m_padstackHoleDefDetailModel(new PadstackHoleDetailModel(this)),
    m_padstackHoleDefDetailView(new QTableView),
    m_navigationWidget(new QWidget),
    m_auxiliaryWidget(new QWidget),
    m_detailViewStackedWidget(new QStackedWidget)
{
    m_padstackDefListView->setModel(m_padstackDefListModel);
    m_padstackDefListView->setAlternatingRowColors(true);

    m_layerListWidget->setLayerListModel(m_layerListModel);

    m_padUseListModel->setAllStates(true);
    m_padUseListView->setModel(m_padUseListModel);
    m_padUseListView->setAlternatingRowColors(true);

    m_padstackPadDefDetailView->setModel(m_padstackPadDefDetailModel);
    m_padstackPadDefDetailView->setAlternatingRowColors(true);
    m_padstackPadDefDetailView->horizontalHeader()->setStretchLastSection(true);
    m_padstackPadDefDetailView->setSelectionBehavior(QTableView::SelectRows);

    m_padstackHoleDefDetailView->setModel(m_padstackHoleDefDetailModel);
    m_padstackHoleDefDetailView->setAlternatingRowColors(true);
    m_padstackHoleDefDetailView->horizontalHeader()->setStretchLastSection(true);
    m_padstackHoleDefDetailView->setSelectionBehavior(QTableView::SelectRows);

    m_graphicsScene->setLayerModel(m_layerListModel);
    m_graphicsView->setScene(m_graphicsScene);
    m_graphicsView->setBackgroundBrush(Solarized::backgroundHighlight);
    m_graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    m_graphicsView->setRenderHint(QPainter::Antialiasing);

    m_navigationWidget->setLayout(new QVBoxLayout);
    m_navigationWidget->layout()->addWidget(m_padstackDefListView);

    m_auxiliaryWidget->setLayout(new QVBoxLayout);
    m_auxiliaryWidget->layout()->addWidget(m_layerListWidget);
    m_auxiliaryWidget->layout()->addWidget(m_padUseListView);
    m_auxiliaryWidget->layout()->addWidget(m_detailViewStackedWidget);
    m_detailViewStackedWidget->addWidget(m_padstackPadDefDetailView);
    m_detailViewStackedWidget->addWidget(m_padstackHoleDefDetailView);

    connect(m_padstackDefListView->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, [this](const QModelIndex &current, const QModelIndex &)
    {
        setCurrentPadstackDef(m_padstackDefListModel->padstackDef(current));
    });
    connect(m_padUseListView->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, [this](const QModelIndex &current, const QModelIndex &)
    {
        updateDetailView(m_currentLayer, m_padUseListModel->padUse(current));
    });
    connect(m_padUseListModel, &PadUseListModel::padUseStateChanged,
            this, [this](Ipc2581b::PadUse use, bool state)
    {
        for (auto layer: m_layerListModel->layers())
            if (m_graphicsItemMap.value(layer).contains(use))
                m_graphicsItemMap.value(layer).value(use)->setVisible(state);
    });
}

void PadstackDefViewer::setCurrentPadstackDef(Ipc2581b::PadstackDef *padstackDef)
{
    static const QList<QColor> colors({Solarized::yellow,
                                       Solarized::cyan,
                                       Solarized::orange,
                                       Solarized::magenta,
                                       Solarized::red,
                                       Solarized::violet,
                                       Solarized::blue,
                                       Solarized::green});

    // Cleanup Graphics scene and setup graphics layers
    m_graphicsScene->clearAll();

    // Collect used layers, no duplicates
    QList<Ipc2581b::Layer *> usedLayers;
    for (auto pad: padstackDef->padstackPadDefList)
    {
        auto layer = m_allLayerMap.value(pad->layerRef);
        if (!usedLayers.contains(layer))
            usedLayers.append(layer);
    }

    // Order used layers according to original ordering
    for (auto layer: m_allLayerList)
    {
        if (usedLayers.contains(layer))
            usedLayers.append(layer);
    }

    // setup layer list model
    m_layerListModel->setLayerList(usedLayers);
    m_layerListModel->setAllLayerVisibility(true);
    for (int index = 0; index < m_layerListModel->layerCount(); index++)
        m_layerListModel->setLayerColor(index, colors.value(index % colors.count()));

    //
    m_currentPadstackDef = padstackDef;

    // Create path for all holes
    QList<QPainterPath> holePathList;
    for (auto hole: m_currentPadstackDef->padstackHoleDefList)
    {
        QPointF location(hole->x,
                         hole->y);
        qreal radius = hole->diameter/2.0;

        QPainterPath path;
        path.addEllipse(location, radius, radius);
        holePathList.append(path);
    }

#if 0
    // FIXME: Stop trying to get "clever and beautiful", use GraphicsItemFactory and
    // render holes as circles on dedicated layer (could add a checkbox for special
    // render tricks)
    // Create graphics items for pads, per layer and per pad use
    m_graphicsItemMap.clear();
    for (auto pad: m_currentPadstackDef->padstackPadDefList)
    {
        auto layer = m_layerListModel->layer(pad->layerRef);
        auto layerIndex = m_layerListModel->layerSequenceOrder(pad->layerRef);

        QTransform transform;
        if (pad->xformOptional.hasValue)
            transform = m_graphicsItemFactory->createTransform(pad->xformOptional.value);

        QPointF location(pad->location->x,
                         pad->location->y);
        auto feature = m_graphicsItemFactory->createGraphicsFeature(pad->feature);

        // FIXME: trick to avoid circle becoming octagon, make sure coordinates
        // are big enough to reduce the visual effect of bezier and arc flatening
        // UPDATE: GraphicsItemFactory now scale everything to microns
        // FIXME: Still need to take into account feature location and transform...
        // See testcase2.xml, DVI_34P58A8W and BRKT_TAB_125P_18 for examples
        QTransform scale = QTransform::fromScale(1000000, 1000000);
        auto path = scale.map(feature->shape());
        for (auto holePath: holePathList)
            path -= scale.map(holePath);
        //feature->setPath(scale.inverted().map(feature.path));

        auto *item = new GraphicsShapeItem();
        item->setColor(m_usedLayerColorList.value(layerIndex));
        item->setFillStyle(feature.brush.style());
        item->setPath(feature.path);
        item->setTransform(transform);
        item->setPos(location);

        if (m_graphicsItemMap[layer].contains(pad->padUse))
        {
            qDebug() << QString("Pad use already defined!");
        }
        m_graphicsItemMap[layer][pad->padUse] = item;

        m_graphicsScene->addLayerItem(layerIndex, item);
    }
#endif

    // auto-adjust scene rect
    auto itemsRect = m_graphicsScene->layersBoundingRect();
    auto shapeRect = QTransform::fromScale(1.2, 1.2).map(itemsRect).boundingRect();
    auto sceneRect = QTransform::fromScale(10, 10).map(itemsRect).boundingRect();
    m_graphicsScene->setSceneRect(sceneRect);

    // Buildup shape layer
#if 0
    QPainterPath path;
    path.addRect(shapeRect);
    for (auto hole: holePathList)
        path.addPath(hole);
    m_graphicsScene->setBoardItem(path);
#endif

    // TODO: Apply current settings for opacity and drop shadow

    // auto-adjust GraphicsView
    m_graphicsView->fitInView(shapeRect, Qt::KeepAspectRatio);
    m_graphicsView->scale(0.8, 0.8);

    // auto-select first layer and first pad use
    m_layerListWidget->setCurrentLayer(m_layerListModel->index(0, 0));
    m_padUseListView->selectionModel()->setCurrentIndex(m_padUseListModel->index(0, 0),
                                                       QItemSelectionModel::ClearAndSelect);
}

void PadstackDefViewer::setCurrentLayer(const Ipc2581b::Layer *layer)
{
    m_currentLayer = layer;
}

void PadstackDefViewer::clear()
{
    m_layerListModel->setLayerList(QList<Ipc2581b::Layer*>());
    m_allLayerList.clear();
    m_allLayerMap.clear();
    m_padstackDefListModel->setPadstackDefList(QList<Ipc2581b::PadstackDef*>());
    m_padstackDefList.clear();
    m_graphicsScene->clearAll();

}

void PadstackDefViewer::updateDetailView(const Ipc2581b::Layer *layer, Ipc2581b::PadUse use)
{
    for (auto pad: m_currentPadstackDef->padstackPadDefList)
    {
        auto thisLayer = m_allLayerMap.value(pad->layerRef);

        if (thisLayer != layer)
            continue;

        if (pad->padUse != use)
            continue;

        m_padstackPadDefDetailModel->setPadstackPadDef(pad);
        m_detailViewStackedWidget->setCurrentWidget(m_padstackPadDefDetailView);
        return;
    }

    qDebug() << QString("No padstack pad definition found for %1/%2")
                .arg(layer->name).arg(Ipc2581b::EnumTranslator::padUseText(use));
    m_padstackPadDefDetailModel->setPadstackPadDef(nullptr);
}

void PadstackDefViewer::setDocument(Ipc2581b::Ipc2581 *ipc)
{
    m_ipc = ipc;

    clear();

    if (m_ipc == nullptr)
        return;

    m_graphicsItemFactory->loadDictionaries(m_ipc->content);

    m_allLayerList = m_ipc->ecad->cadData->layerList;
    for (auto layer: m_allLayerList)
        m_allLayerMap.insert(layer->name, layer);

    m_padstackDefList = m_ipc->ecad->cadData->stepList.first()->padStackDefList;
    m_padstackDefListModel->setPadstackDefList(m_padstackDefList);
    m_padstackDefListView->selectionModel()->select(m_padstackDefListModel->index(0, 0),
                                                    QItemSelectionModel::ClearAndSelect);
}

QWidget *PadstackDefViewer::navigationWidget() const
{
    return m_navigationWidget;
}

QWidget *PadstackDefViewer::auxiliaryWidget() const
{
    return m_auxiliaryWidget;
}

void PadstackDefViewer::enableAntiAlias(bool enabled)
{
    m_graphicsView->enableAntiAliasing(enabled);
}

void PadstackDefViewer::enableTransparentLayers(bool enabled)
{
    m_graphicsScene->enableTransparentLayers(enabled);
}

void PadstackDefViewer::enableDropShadowEffect(bool enabled)
{
    m_graphicsScene->enableDropShadowEffect(enabled);
}

void PadstackDefViewer::enableOpenGL(bool enabled)
{
    m_graphicsView->enableOpenGL(enabled);
}

void PadstackDefViewer::setLevelOfDetails(LevelOfDetails lod)
{
    switch (lod)
    {
        case LevelOfDetails::High:
            m_graphicsScene->setMinimumRenderSize(0.0);
            break;
        case LevelOfDetails::Medium:
            m_graphicsScene->setMinimumRenderSize(1.0);
            break;
        case LevelOfDetails::Low:
            m_graphicsScene->setMinimumRenderSize(2.0);
            break;
    }
}

QWidget *PadstackDefViewer::centralWidget() const
{
    return m_graphicsView;
}
